/*************************************************************************** 
 * (c) 2006 Dmitry Osinovsky, osdmsoftware@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Lesser General Public License as        *
 *   published by the Free Software Foundation; either version 2 of the    *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 ***************************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using System.Drawing.Imaging;

namespace OsdmSoftware.CommonLibrary
{
    public delegate void DrawItemHandler(Graphics gs, object obj, int index, int x, int y, bool selected);
    // Base custom control for DrawFontList
    public class OwnerDrawnListBox: Control
    {
        public event DrawItemHandler DrawItem;
        int scrollWidth = 20;
        int itemHeight = 10;
        int selectedIndex = -1;

        Bitmap offScreen;
        VScrollBar vs;
        protected IList items;
        Color highlightBackColor;
        Color highlightForeColor;

        public Color HighlightBackColor { get { return highlightBackColor; } set { highlightBackColor = value; } }
        public Color HighlightForeColor { get { return highlightForeColor; } set { highlightForeColor = value; } }

        public OwnerDrawnListBox()
        {
            this.vs = new VScrollBar();
            this.vs.Parent = this;
            this.vs.Visible = false;
            this.vs.SmallChange = 1;
            this.vs.ValueChanged += new EventHandler(this.ScrollValueChanged);
            this.vs.Minimum = 0;

            this.items = new ArrayList();
        }

        /*protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
        {
            itemHeight = (int) Math.Round(itemHeight * factor.Height);
            scrollWidth = (int) Math.Round(scrollWidth * factor.Width);
            base.ScaleControl(factor, specified);
        }*/

        protected override bool ScaleChildren
        {
            get
            {
                return false;
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (this.offScreen != null)
                this.offScreen.Dispose();

            base.Dispose(disposing);
        }

        public void UpdateItems()
        {
            int viewableItemCount = this.ClientSize.Height / this.ItemHeight;
            if (this.offScreen != null)
                this.offScreen.Dispose();
            // Determine if scrollbars are needed
            if (this.items.Count > viewableItemCount)
            {
                this.vs.Visible = true;
                this.vs.LargeChange = viewableItemCount;
                this.offScreen = new Bitmap(this.ClientSize.Width - scrollWidth, this.ClientSize.Height);
            }
            else
            {
                this.vs.Visible = false;
                this.vs.LargeChange = this.items.Count;
                this.offScreen = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
            }

            //this.vs.Value = 0;
            this.vs.Minimum = 0;
            this.vs.Maximum = this.items.Count - 1;

            if (this.vs.Value < 0 && this.items.Count > 0)
            {
                this.vs.Value = 0;
            }
        }

        public IList Items
        {
            get
            {
                return this.items;
            }
        }

        protected Bitmap OffScreen
        {
            get
            {
                return this.offScreen;
            }
        }

        protected VScrollBar VScrollBar
        {
            get
            {
                return this.vs;
            }
        }

        public event EventHandler SelectedIndexChanged;
        //public event EventHandler DoubleClick;

        // Raise the SelectedIndexChanged event
        protected virtual void OnSelectedIndexChanged(EventArgs e)
        {
            //return;
            if (this.SelectedIndexChanged != null)
                this.SelectedIndexChanged(this, e);
        }

        public int GetItemIndexAt(int x, int y)
        {
            int sel = this.vs.Value + (y / this.ItemHeight);
            if (sel < 0 || sel >= items.Count)
                return -1;
            else
                return sel;
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            int sel = GetItemIndexAt(e.X, e.Y);
            if (sel == -1)
                return;
            if (this.SelectedIndex == sel)
            {
                //OnDoubleClick(EventArgs.Empty);
                /*
                if (DoubleClick != null)
                    DoubleClick(this, EventArgs.Empty);*/
            }
            else
                this.SelectedIndex = sel;

            // Invalidate the control so we can draw the item as selected.
            this.Refresh();
            base.OnMouseDown(e);
        }

        // Get or set index of selected item.
        public int SelectedIndex
        {
            get
            {
                return this.selectedIndex;
            }

            set
            {
                this.selectedIndex = value;

                this.OnSelectedIndexChanged(EventArgs.Empty);
            }
        }

        public object SelectedItem
        {
            get
            {
                if (selectedIndex == -1)
                    return null;
                else
                    return items[selectedIndex];
            }
            set
            {
                SelectedIndex = items.IndexOf(value);
            }
        }

        protected void ScrollValueChanged(object o, EventArgs e)
        {
            this.Refresh();
        }

        public virtual int ItemHeight
        {
            get
            {
                return this.itemHeight;
            }

            set
            {
                this.itemHeight = value;
            }
        }

        // If the requested index is before the first visible index then set the
        // first item to be the requested index. If it is after the last visible
        // index, then set the last visible index to be the requested index.
        public void EnsureVisible(int index)
        {
            if (index < this.vs.Value)
            {
                this.vs.Value = index;
                this.Refresh();
            }
            else if (index >= this.vs.Value + this.DrawCount)
            {
                this.vs.Value = index - this.DrawCount + 1;
                this.Refresh();
            }
        }


        // Need to set focus to the control when it
        // is clicked so that keyboard events occur.
        protected override void OnClick(EventArgs e)
        {
            //return;
            this.Focus();
            base.OnClick(e);
        }

        // Selected item moves when you use the keyboard up/down keys.
        protected override void OnKeyDown(KeyEventArgs e)
        {
            //return;
            switch (e.KeyCode)
            {
                case Keys.Down:
                    if (this.SelectedIndex < this.vs.Maximum)
                    {
                        EnsureVisible(++this.SelectedIndex);
                        this.Refresh();
                    }
                    break;
                case Keys.Up:
                    if (this.SelectedIndex > this.vs.Minimum)
                    {
                        EnsureVisible(--this.SelectedIndex);
                        this.Refresh();
                    }
                    break;
                case Keys.PageDown:
                    this.SelectedIndex = Math.Min(this.vs.Maximum, this.SelectedIndex + this.DrawCount);
                    EnsureVisible(this.SelectedIndex);
                    this.Refresh();
                    break;
                case Keys.PageUp:
                    this.SelectedIndex = Math.Max(this.vs.Minimum, this.SelectedIndex - this.DrawCount);
                    EnsureVisible(this.SelectedIndex);
                    this.Refresh();
                    break;
                case Keys.Home:
                    this.SelectedIndex = 0;
                    EnsureVisible(this.SelectedIndex);
                    this.Refresh();
                    break;
                case Keys.End:
                    this.SelectedIndex = this.items.Count - 1;
                    EnsureVisible(this.SelectedIndex);
                    this.Refresh();
                    break;
            }

            base.OnKeyDown(e);
        }

        protected override void OnKeyPress(KeyPressEventArgs e)
        {
            switch (e.KeyChar)
            {
                case (char) Keys.Enter:
                case (char) Keys.F23:
                    OnClick(EventArgs.Empty);
                    OnDoubleClick(EventArgs.Empty);
                    break;
            }
            base.OnKeyPress(e);
        }

        // Calculate how many items we can draw given the height of the control.
        protected int DrawCount
        {
            get
            {
                if (this.vs.Value + this.vs.LargeChange > this.vs.Maximum)
                    return this.vs.Maximum - this.vs.Value + 1;
                else
                    return this.vs.LargeChange;
            }
        }

        protected override void OnResize(EventArgs e)
        {
            //return;
            Graphics gr = this.CreateGraphics();
            //int size = Screen.PrimaryScreen.Bounds.Width;
            int dpi = (int) gr.DpiX;
            gr.Dispose();

            scrollWidth = 10 * dpi / 96;

            this.vs.Bounds = new Rectangle(this.ClientSize.Width - scrollWidth,
                0,
                scrollWidth,
                this.ClientSize.Height);

            UpdateItems();
            base.OnResize(e);
        }

        // Determine what the text color should be
        // for the selected item drawn as highlighted
        Color CalcTextColor(Color backgroundColor)
        {
            if (backgroundColor.Equals(Color.Empty))
                return Color.Black;

            int sum = backgroundColor.R + backgroundColor.G + backgroundColor.B;

            if (sum > 256)
                return Color.Black;
            else
                return Color.White;
        }

        protected virtual void OnDrawItem(Graphics gr, object item, int index, int x, int y, bool selected)
        {
            if (DrawItem != null)
                DrawItem(gr, item, index, x, y, selected);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            //return;
            // The base class contains a bitmap, offScreen, for constructing
            // the control and is rendered when all items are populated.
            // This technique prevents flicker.
            if (this.OffScreen == null)
                return;

            Graphics gOffScreen = Graphics.FromImage(this.OffScreen);
            //Graphics gOffScreen = e.Graphics;
            gOffScreen.FillRectangle(new SolidBrush(this.BackColor), this.ClientRectangle);

            int itemTop = 0;

            // Draw the items in the list.
            for (int n = this.VScrollBar.Value; n < this.VScrollBar.Value + DrawCount; n++)
            {
                if (n >= this.Items.Count || n < 0)
                    break;

                //try
                {
                    OnDrawItem(gOffScreen, this.Items[n], n, 0, itemTop, n == this.SelectedIndex);
                }
                /*catch
                {
                    MessageBox.Show("Error at " + n.ToString() + " count " + this.Items.Count.ToString());
                    throw;
                }*/
                itemTop += this.ItemHeight;
            }

            // Draw the list box
            e.Graphics.DrawImage(this.OffScreen, 0, 0);

            gOffScreen.Dispose();
        }

        public void DrawBitmapAndText(Graphics gs, Image bmp, string s, int x, int y,
            bool selected, int scale, Color imageBgColor)
        {
            DrawBitmapAndText(gs, bmp, s, x, y, selected, scale, imageBgColor, this.ForeColor, -1);
        }

        public void DrawBitmapAndText(Graphics gs, Image bmp, string s, int x, int y,
            bool selected, int scale, Color imageBgColor, Color fontColor)
        {
            DrawBitmapAndText(gs, bmp, s, x, y, selected, scale, imageBgColor, fontColor);
        }

        public void DrawBitmapAndText(Graphics gs, Image bmp, string s, int x, int y,
            bool selected, int scale, Color imageBgColor, Color fontColor, int maxx)
        {
            int x1 = x + 1;
            if (bmp != null)
            {
                x1 = x + bmp.Width * scale + 2;
            }
            SizeF s1 = gs.MeasureString(s, this.Font);
            int yoffset = 0;
            if (s1.Height < this.ItemHeight)
                yoffset = (this.ItemHeight - Convert.ToInt32(s1.Height)) / 2;

            if (maxx != -1)
                s = Utility.CutDownStringToFitWidth(s, gs, this.Font, maxx - x1 + 1 - 2);

            if (selected && !highlightBackColor.IsEmpty)
            {
                gs.FillRectangle(new SolidBrush(highlightBackColor), x1 - 1, yoffset + y - 1, Convert.ToInt32(s1.Width) + 2, Convert.ToInt32(s1.Height) + 2);
            }

            //x1 = x + 1;
            if (bmp != null)
            {
                ImageAttributes imageAttr = new ImageAttributes();
                imageAttr.SetColorKey(imageBgColor, imageBgColor);
                gs.DrawImage(bmp,
                    new Rectangle(x, y + (this.ItemHeight - bmp.Height * scale) / 2, bmp.Width * scale, bmp.Height * scale),
                    0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel, imageAttr);
                //x1 = x + bmp.Width * scale + 2;
            }


            Brush b;

            if (selected && !HighlightForeColor.IsEmpty)
                b = new SolidBrush(HighlightForeColor);
            else
                b = new SolidBrush(fontColor);
            /*SizeF s1 = gs.MeasureString(s, this.Font);
            int yoffset = 0;
            if (s1.Height < this.ItemHeight)
                yoffset = (this.ItemHeight - Convert.ToInt32(s1.Height)) / 2;*/
            gs.DrawString(s, this.Font, b, x1, yoffset + y);

            if (selected)
            {
                if (Utility.RGBEqual(this.BackColor, Color.White))
                    gs.DrawRectangle(new Pen(Color.DimGray), x1 - 1, yoffset + y - 1, Convert.ToInt32(s1.Width) + 2, Convert.ToInt32(s1.Height) + 2);
                else
                    gs.DrawRectangle(new Pen(Color.White), x1 - 1, yoffset + y - 1, Convert.ToInt32(s1.Width) + 2, Convert.ToInt32(s1.Height) + 2);
            }
        }

        protected override void OnPaintBackground(PaintEventArgs e)
        {
            // do not call base method to prevent flickering!
        }

    }
}
